const {
	AsyncSeriesBailHook,
	SyncWaterfallHook,
	SyncBailHook,
	SyncHook,
	HookMap
} = require("tapable");

class NormalModuleFactory extends ModuleFactory {
    constructor({ context, fs, resolverFactory, options, associatedObjectForCache }) {
        super();
        this.hooks = Object.freeze({
            /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
            resolve: new AsyncSeriesBailHook(['resolveData']),
            /** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
            resolveForScheme: new HookMap(
                () => new AsyncSeriesBailHook(['resourceData', 'resolveData'])
            ),
            /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
            factorize: new AsyncSeriesBailHook(['resolveData']),
            /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
            beforeResolve: new AsyncSeriesBailHook(['resolveData']),
            /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
            afterResolve: new AsyncSeriesBailHook(['resolveData']),
            /** @type {AsyncSeriesBailHook<[ResolveData["createData"], ResolveData], TODO>} */
            createModule: new AsyncSeriesBailHook(['createData', 'resolveData']),
            /** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData], TODO>} */
            module: new SyncWaterfallHook(['module', 'createData', 'resolveData']),
            createParser: new HookMap(() => new SyncBailHook(['parserOptions'])),
            parser: new HookMap(() => new SyncHook(['parser', 'parserOptions'])),
            createGenerator: new HookMap(() => new SyncBailHook(['generatorOptions'])),
            generator: new HookMap(() => new SyncHook(['generator', 'generatorOptions'])),
        });

        this.context = context || '';
        this.fs = fs;

        this.hooks.factorize.tapAsync(
            {
                name: 'NormalModuleFactory',
                stage: 100,
            },
            (resolveData, callback) => {
                this.hooks.callAsync(resolveData, (err, result) => {
                    if (err) return callback(err);

                    this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
                        if (err) return callback(err);

                        // Ignored
                        if (result === false) return callback();

                        const createData = resolveData.createData;

                        this.hooks.createModule.callAsync(
                            createData,
                            resolveData,
                            (err, createdModule) => {
                                if (!createdModule) {
                                    if (!resolveData.request) {
                                        return callback(new Error('Empty dependency (no request)'));
                                    }

                                    createdModule = new NormalModule(createData);
                                }

                                createdModule = this.hooks.module.call(
                                    createdModule,
                                    createData,
                                    resolveData
                                );

                                return callback(null, createdModule);
                            }
                        );
                    });
                })
            }
        );
    }


}

module.exports = NormalModuleFactory;
